-- Matías Blanco & Sergio Pérez
-- Aeropuerto. Práctica 2 - Citas en ADA

with Ada.Text_Io ; use Ada.Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
with Ada.Numerics.Float_Random ; use Ada.Numerics.Float_Random;

procedure AeroCitasAda is
   -- Costantes y variables
   MAXAVIONES : constant integer := 50;
   -- Definimos el tipo enumerador
   type EstadoPista is (Aterrizar, Despegar, Vacia);
   type EstadoAvion is (QuiereAterrizar, QuiereDespegar, OtraCosa);

   -- Definimos el tipo registro Pista
   type Pista is
   record
      id : integer;
      estado : EstadoPista;
      naviones : integer; -- Numero de aviones en la pista
   end record;

   -- Definicion de tipos
   type TPistas is array(1..2) of Pista;
   type TestadosAviones is array(1..MAXAVIONES) of EstadoAvion;

   -- Definimos las tareas
   task type TorreControl is
      entry PedirPistaAterrizar(idAvion : in integer; pista : out integer);
      entry PedirPistaDespegar(idAvion : in integer; pista : out integer);
      entry DejarPista(idPista : in integer);
   end TorreControl;

   task type Avion(idAvion : integer) is
      entry PuedeDespegar(idPista : in integer);
      entry PuedeAterrizar(idPista : in integer);
   end Avion;

   -- Definimos a TorreControl para que controle a las aviones
   control : TorreControl;

   type PAvion is access Avion; -- Definitos el tipo puntero de Avion
   -- Definimos el array de punteros de aviones
   aviones: array(1..MAXAVIONES) of PAvion;

   -- Definimos el procedimiento para poder gestionar las aviones que esten esperando
   procedure GestorDeEsperas(p : in out TPistas; estAviones : in out TestadosAviones; esperandoAterrizar : in out integer) is
   begin
      -- Recorremos las aviones para ver si alguna quiere algo
      for av in 1..MAXAVIONES loop
         -- Si quiere aterrizar vemos si hay alguna pista para ello y si es asi se la damos
         if estAviones(av) = QuiereAterrizar then
            for i in 1..2 loop
               if (p(i).estado = Vacia) and (estAviones(av) = QuiereAterrizar) then
                  p(i).estado := Aterrizar;
                  p(i).naviones := p(i).naviones + 1;
                  estAviones(av) := OtraCosa;
                  aviones(av).all.PuedeAterrizar(i);
                  esperandoAterrizar := esperandoAterrizar - 1;
               end if;
            end loop;
         -- Si quiere despegar vemos si hay alguna pista para ello y si es asi se la damos
         elsif estAviones(av) = QuiereDespegar and esperandoAterrizar = 0 then
            for i in 1..2 loop
               if ((p(i).estado = Vacia) or (p(i).estado = Despegar and p(i).naviones < 3)) and estAviones(av) = QuiereDespegar then
                  p(i).estado := Despegar;
                  p(i).naviones := p(i).naviones + 1;
                  estAviones(av) := OtraCosa;
                  aviones(av).all.PuedeDespegar(i);
               end if;
            end loop;
         end if;
      end loop;
   end GestorDeEsperas;

   task body TorreControl is      
      pts : TPistas;
      estAviones : TEstadosAviones;
      esperandoAterrizar : integer := 0;
   begin
      -- Inicilizamos las pistas
      pts(1).id := 1;
      pts(1).estado := Vacia;
      pts(1).naviones := 0;
      pts(2).id := 2;
      pts(2).estado := Vacia;
      pts(2).naviones := 0;
      -- inicializamos el estado de las aviones
      for i in 1..MAXAVIONES loop
         estAviones(i) := OtraCosa;
      end loop;

      loop
         select
            accept PedirPistaAterrizar(idAvion : in integer; pista : out integer) do
               estAviones(idAvion) := QuiereAterrizar;
               pista := 0;
               -- Miramos las pistas para ver si hay alguna en la que se pueda aterrizar
               for i in 1..2 loop
                  if (pts(i).estado = Vacia) and (estAviones(idAvion) = QuiereAterrizar) then
                     pts(i).estado := Aterrizar;
                     pts(i).naviones := pts(i).naviones + 1;
                     estAviones(idAvion) := OtraCosa;
                     pista := i;
                  end if;
               end loop;
               if estAviones(idAvion) = QuiereAterrizar then
                  esperandoAterrizar := esperandoAterrizar + 1;
               end if;
            end PedirPistaAterrizar;
         or when esperandoAterrizar = 0 => -- Si hay aviones que quieran aterrizar no se puede despegar
            accept PedirPistaDespegar(idAvion : in integer; pista : out integer) do
               estAviones(idAvion) := QuiereDespegar;
               pista := 0;
               -- Miramos las pistas para ver si hay alguna en la que se pueda despegar
               for i in 1..2 loop
                  if ((pts(i).estado = Vacia) or (pts(i).estado = Despegar and pts(i).naviones < 3)) and estAviones(idAvion) = QuiereDespegar then
                     pts(i).estado := Despegar;
                     pts(i).naviones := pts(i).naviones + 1;
                     estAviones(idAvion) := OtraCosa;
                     pista := i;
                  end if;
               end loop;
            end PedirPistaDespegar;
         or
            accept DejarPista(idPista : in integer) do
               -- Liberamos de una avión la pista indicada
               pts(idPista).naviones := pts(idPista).naviones - 1;
               if pts(idPista).naviones = 0 then
                  pts(idPista).estado := Vacia;
               end if;
            end DejarPista;
            GestorDeEsperas(pts, estAviones, esperandoAterrizar);
         or
            terminate;
         end select;
      end loop;
   end TorreControl;

   task body Avion is
      id: integer;
      pista: integer;
   begin
      -- Inicializamos los valores
      id := idAvion;
      loop
         -- Despegar
         Put("-- El avion ");Put(id,0);Put(" quiere despegar.");New_Line;
         control.PedirPistaDespegar(id,pista);
         if pista = 0 then
            accept PuedeDespegar(idPista : in integer) do
               pista := idPista;
            end PuedeDespegar;
         end if;
         Put("++> El avion ");Put(id,0);Put(" esta despegando en la pista ");Put(pista,0);New_Line;
         delay 1.0; -- Tiempo que tarda en despegar
         Put("--> El avion ");Put(id,0);Put(" ha despegado en la pista ");Put(pista,0);New_Line;
         control.DejarPista(pista);

         delay 3.0; -- Tiempo que esta volando

         -- Aterrizar
         Put("-- El avion ");Put(id,0);Put(" quiere aterrizar.");New_Line;
         control.PedirPistaAterrizar(id,pista);
         if pista = 0 then
            accept PuedeAterrizar(idPista : in integer) do
               pista := idPista;
            end PuedeAterrizar;
         end if;
         Put("<++ El avion ");Put(id,0);Put(" esta aterrizando en la pista ");Put(pista,0);New_Line;
         delay 1.0; -- Tiempo que tarda en aterrizar
         Put("<-- El avion ");Put(id,0);Put(" ha aterrizado en la pista ");Put(pista,0);New_Line;
         control.DejarPista(pista);

         delay 5.0; -- Tiempo de descanso
      end loop;
   end Avion;
begin
   Put("---- COMIENZAN A SALIR AVIONES");New_Line;New_Line;
   for i in 1..MAXAVIONES loop
      aviones(i) := new Avion(i);
   end loop;
end AeroCitasAda;
